package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;


@Service
@Slf4j
@RequiredArgsConstructor
public class ISignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;

    @Override
    public SignResultVO addSignRecords() {
        //1.签到
        //1.1获取用户id
        Long userId = UserContext.getUser();
        //1.2计算日期
        LocalDate now = LocalDate.now();
        //1.3拼接位图key
        String key= RedisConstants.SIGN_RECORD_kEY_PREFIX+userId+now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        log.info("key:{}",key);
        //1.4计算偏移量offset（月中天减1）
        int offset = now.getDayOfMonth() - 1;
        //1.5保存至缓存，setBit返回值（0表示没有签过道，1表示签过道）
        Boolean isSign = redisTemplate.opsForValue().setBit(key, offset,true);
        log.info("签到是否成功：{}",isSign);
        if (BooleanUtils.isTrue(isSign)){
            throw new BadRequestException("不可以重复签到");
        }
        //2.计算连续签到的天数
        int signDays= countSignDays(key,now.getDayOfMonth());
        log.info("signDays:{}",signDays);
        //2.1统计连续签到天数
        int count = 0;
        while ((signDays & 1) == 1){
            count++;
            signDays >>>= 1;
        }
        //3.计算签到得分
        int rewardPoints=0;
        log.info("累计签到天数：{}",count);
        switch (count){
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        //4.保存积分明细记录,发送MQ消息通知业务方增加积分
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId,rewardPoints+1)
        );
        //5.封装VO返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(count);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }

    @Override
    public Byte[] querySignRecords() {
        //1.1获取用户id
        Long userId = UserContext.getUser();
        //1.2计算日期
        LocalDate now = LocalDate.now();
        //1.3拼接位图key
        String key= RedisConstants.SIGN_RECORD_kEY_PREFIX+userId+now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        log.info("key:{}",key);
        //1.4获取月中天
        int dayOfMonth = now.getDayOfMonth();
        //2.查询
        //2.1获取本月到今天的所有签到记录
        int signDays = countSignDays(key, dayOfMonth);
        //2.2健状性处理
        if (signDays==0) {
            return new Byte[0];
        }
        //3.封装数组
        Byte[] arr = new Byte[dayOfMonth];
        int offset=dayOfMonth-1;
        while (offset >= 0){
            arr[offset--]=(byte)(signDays & 1);
            //右移一位继续遍历
            signDays >>>=1;
        }
        return arr;

    }

    private int countSignDays(String key, int length) {
        //1.获取本月到今天的所有签到记录
        List<Long> bitField = redisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(length)).valueAt(0));
        //2.健状性处理
        if (CollUtils.isEmpty(bitField)) {
            return 0;
        }
        //3.获取总天数
        return bitField.get(0).intValue();
    }
}
